﻿(function ($) {
    var constant = {
        css: {
            rootClass: 'treeView',
            checkboxTypeClass: 'node-checkbox',
            radioTypeClass: 'node-radio',
            expandClass: 'node-expand',
            closeClass: 'node-closed',
            checkboxClass: 'checkbox',
            radioClass: 'radio',

            showboxClass: 'box-show',
            hideboxClass: 'box-hide',
            checkedClass: 'box-checked',
            notCheckClass: 'box-notcheck',

            titleTextClass: 'titleText',
            hasChildClass: 'node-haschild',
            nodeCheckedClass: 'node-checked'
        }
    }
    //Array opration

    Array.prototype.filter = Array.prototype.filter || function (predicate) {
        var results = [];
        for (var i = 0; i < this.length; i++) {
            if (predicate(this[i], i, this) === true) results.push(this[i]);
        }
        return results;
    };
    Array.prototype.find = Array.prototype.find || function (predicate) {
        var item = null;
        for (var i = 0; i < this.length; i++) {
            if (predicate(this[i], i, this) === true) return this[i];
        }
        return item;
    };
    Array.prototype.forEach = Array.prototype.forEach || function (predicate) {
        for (var i = 0; i < this.length; i++) {
            predicate(this[i], i, this);
        }
    }
    Array.prototype.remove = function (item) {
        var index = this.indexOf(item);
        if (index > -1) {

            this.splice(index, 1);

        }
    }
    Array.prototype.removeWhere = function (predicate) {
        var items = this.filter(predicate);
        var ths = this;
        items.forEach(function (item) {
            ths.remove(item);
        });
    }




    var treeView = function (container, listData, fieldSelectors) {

        var variable = {
            showCheckbox: false,
            isExpand: true,
            isChecked: false,
            multiSelect: true,
            fieldName: ('n_' + Math.random() + '_' + new Date().getTime()).replace('.', '')
        }
        var callbacks = {
            nodeClick: undefined,//function (ev,node) { },
            nodeMouseEnter: undefined,
            nodeMouseLeave: undefined, //function (ev,node) { },
            nodeCheckChange: undefined//function (ev,node) { },
        }

        var treeNode = function (id, name, icon, level, parentNode, bindingData, isCategory) {
            var children = [];
            var ths = this;
            this.id = id;
            this.name = name || null;
            this.icon = icon || null,
            this.level = level || 0;
            this.parentNode = parentNode || null;
            this.bindingData = bindingData || null;
            
            this.isCategory = (isCategory == undefined || isCategory==null)?true:isCategory;
            this.element = null;
            var $node, $titleLine, $icon, $label, $checkbox, $childrenDiv, $childrenDl, $i;
            var showcheckbox = false, isrender = false, ischecked = false, isexpand = true;
            this.children = function () {
                return children;
            }
            this.addChildren = function (nodes) {
                for (var i = 0; i < nodes.length; i++) {
                    var child = nodes[i];
                    child.level = ths.level + 1;
                    children.push(child);

                    if (AllNodes.indexOf(nodes[i]) == -1) {
                        AllNodes.push(nodes[i]);
                    }
                    if (isrender) {
                        renderChildren(nodes);
                        if (!isexpand) {
                            ths.expand();
                        }
                    }
                }
                return ths;
            }
            this.isCheck = function () {
                return ischecked;
            }
            this.isExpand = function () {
                return isexpand;
            }
            this.check = function () {
                ischecked = true;
                if (isrender) {
                    var oldVal = $checkbox.get(0).checked;
                    $checkbox.get(0).checked = ischecked;
                    if (oldVal != ischecked) {
                        $checkbox.change();
                    }
                }
                return ths;
            }
            this.checkOnly = function () {
                ischecked = true;
                if (isrender) {
                    $checkbox.get(0).checked = ischecked;
                    setboxUi(ischecked);
                }
                return ths;
            }
            this.uncheckOnly = function () {
                ischecked = false;
                if (isrender) {
                    $checkbox.get(0).checked = ischecked;
                    setboxUi(ischecked);
                }
                return ths;
            }
            this.uncheck = function () {
                ischecked = false;
                if (isrender) {

                    var oldVal = $checkbox.get(0).checked;
                    if (variable.multiSelect) {
                        $checkbox.get(0).checked = ischecked;
                        if (oldVal != ischecked) {
                            $checkbox.change();
                        }
                    }
                    else {
                        $checkbox.get(0).checked = ischecked;
                        $checkbox.change();
                    }

                }
                return ths;
            }
            this.toggleCheck = function () {
                if (ischecked) ths.uncheck();
                else ths.check();
                return ths;
            }
            this.showCheckbox = function () {
                showcheckbox = true;
                if (isrender) {
                    $icon.removeClass(constant.css.hideboxClass).addClass(constant.css.showboxClass);
                }
                return ths;
            }
            this.hideCheckbox = function () {
                showcheckbox = false;
                if (isrender) {
                    $icon.removeClass(constant.css.showboxClass).addClass(constant.css.hideboxClass);
                }
                return ths;
            }
            this.expand = function () {
                isexpand = true;

                if (isrender) {
                    $node.removeClass(constant.css.closeClass).addClass(constant.css.expandClass);
                    $node.children('div').show();
                }
                if (ths.parentNode && !ths.parentNode.isExpand()) {
                    ths.parentNode.expand();
                }
                return ths;
            }
            this.close = function () {
                isexpand = false;

                if (isrender) {
                    $node.removeClass(constant.css.expandClass).addClass(constant.css.closeClass);
                    $node.children('div').hide();
                }
                return ths;
            }

            this.toggle = function () {
                if (isexpand) ths.close();
                else ths.expand();
                return ths;
            }
            this.bindEvent = function (eventType, callback, ischeckboxEvent) {

                if (!isrender) {
                    console.log(eventType + ' 请在 treeNode render 之后再 bindEvent.');
                    return;
                }

                var cb = callback;
                if (!ischeckboxEvent) {
                    $label.unbind(eventType).bind(eventType, function () {

                        cb(ths);
                    });
                }
                else {
                    $checkbox.bind('change', function (ev) {
                        ischecked = ev.target.checked;
                        cb(ths);
                    });
                }
                return ths;
            }
            this.destroy = function () {

                if (ths.element) {
                    if (ths.parentNode && ths.parentNode.children().length <= 1) {
                        ths.element.parent().parent().parent().removeClass(constant.css.hasChildClass);
                        ths.element.parent().parent().remove();
                    }
                    ths.element.remove();
                }
                if (ths.parentNode) {
                    ths.parentNode.children().remove(ths);
                }

            }
            function renderChildren(nodes) {
                if (nodes.length > 0) {
                    $node.addClass(constant.css.hasChildClass);
                    $i = $i || $('<i>');
                    $titleLine.append($i);
                    $i.unbind('click').click(ths.toggle);
                    $childrenDiv = $childrenDiv || $('<div>');
                    $childrenDl = $childrenDl || $('<dl>');
                    for (var i = 0; i < nodes.length; i++) {
                        $childrenDl.append(nodes[i].render());
                    }
                    $node.append($childrenDiv.append($childrenDl));
                }

            }
            function setboxUi(ischecked) {
                if (ischecked) {
                    $node.addClass(constant.css.nodeCheckedClass);
                    $icon.removeClass(constant.css.notCheckClass).addClass(constant.css.checkedClass);
                } else {
                    $node.removeClass(constant.css.nodeCheckedClass)
                    $icon.removeClass(constant.css.checkedClass).addClass(constant.css.notCheckClass);
                }
            }
            this.render = function () {
                if (isrender) return;
                ths.element = $node = $('<dd>');
                $titleLine = $titleLine || $('<p>');
                $icon = $('<label>');
                var type = 'checkbox';
                if (variable.multiSelect) {
                    $node.addClass(constant.css.checkboxTypeClass);
                    type = 'checkbox';
                    $icon.addClass(constant.css.checkboxClass)
                }
                else {
                    $node.addClass(constant.css.radioTypeClass);
                    type = 'radio';
                    $icon.addClass(constant.css.radioClass)
                }
                var name = variable.fieldName;
                $checkbox = $('<input>').attr({ 'type': type, 'value': ths.id, 'name': name });
                $icon.append($checkbox);
                $label = $('<span>').addClass(constant.css.titleTextClass);
                $label.append(ths.name);
                $titleLine.append($icon, $label);

                var cornerBefore = $('<label>').addClass('cornerBefore');
                $node.append(cornerBefore, $titleLine);

                $checkbox.change(function (ev) {
                    ischecked = ev.target.checked;
                    setboxUi(ischecked);
                    if (ev.target.checked) {


                        if (!variable.multiSelect) {
                            if (selectedNode) {
                                selectedNode.uncheck();
                            }
                        }
                        selectedNode = ths;
                    }
                    else {
                        if (selectedNode === ths) {
                            selectedNode = null;
                        }

                    }
                    ////触发checkChange事件
                    //if (callbacks.nodeCheckChange && typeof callbacks.nodeCheckChange === 'function') {
                    //    callbacks.nodeCheckChange.call(ths, ev, ths);
                    //}

                });

                showcheckbox = showcheckbox ? true : (variable.showCheckbox ? true : false);
                if (!showcheckbox) {
                    $icon.addClass(constant.css.hideboxClass);
                }
                else {
                    $icon.addClass(constant.css.showboxClass);
                }

                isexpand = !isexpand ? false : (!variable.isExpand ? false : true);
                if (isexpand) {
                    $node.addClass(constant.css.expandClass);
                }
                else {
                    $node.addClass(constant.css.closeClass);
                }

                ischecked = ischecked ? true : (variable.isChecked ? true : false);
                if (ischecked) {
                    $checkbox.get(0).checked = true;
                    $icon.addClass(constant.css.checkedClass);
                    $node.addClass(constant.css.nodeCheckedClass);
                }
                else {
                    $icon.addClass(constant.css.notCheckClass);
                }


                if (children.length > 0) {

                    renderChildren(children);
                }
                isrender = true;


                if (callbacks.nodeClick && typeof callbacks.nodeClick === 'function') {
                    this.bindEvent('click', callbacks.nodeClick);
                }
                if (callbacks.nodeMouseEnter && typeof callbacks.nodeMouseEnter === 'function') {
                    this.bindEvent('mouseenter', callbacks.nodeMouseEnter);
                }
                if (callbacks.nodeMouseLeave && typeof callbacks.nodeMouseLeave === 'function') {
                    this.bindEvent('mouseleave', callbacks.nodeMouseLeave);
                }
                if (callbacks.nodeCheckChange && typeof callbacks.nodeCheckChange === 'function') {
                    this.bindEvent('change', callbacks.nodeCheckChange, true);
                }
                return $node;
            }
            this.refresh = function () {
                if (isrender) {
                    $label.text(ths.name);
                    var type = variable.multiSelect ? 'checkbox' : 'radio';
                    $checkbox.attr({ 'value': ths.id, 'type': type });

                }
                return ths;
            }
        }

        var AllNodes = [/*new treeNode(1, '', '', 0, null, null)*/].slice(0, 0);
        var ths = this;
        var list = listData;
        var IdField = 'id';
        var hasRender = false;
        var selectedNode = null;
        var selectors = {
            idSelector: function (x) { return null; },
            nameSelector: function (x) { return null; },
            iconSelector: function (x) { return null; },
            //levelSelector: function (x) { return 0; },
            parentNodeSelector: function (x) { return null; },
            isCategorySelector: function (x) { return true;}
        }
        var sortExpression = undefined;

        selectors = $.extend({}, selectors, fieldSelectors);

        function sortChildren(node) {
            if (node.children().length > 0) {
                node.children().sort(sortExpression);
                node.children().forEach(function (x) { sortChildren(x); });
            }
        }

        this.orderBy = function (fn) {
            sortExpression = function (a, b) {
                return fn(a) - fn(b);
            };
            rootNodes.sort(sortExpression).forEach(function (x) {
                sortChildren(x);

            });
            if (hasRender) {
                //container.empty();
                this.render();
            }
            return this;
        }
        this.orderByDesc = function (fn) {
            sortExpression = function (a, b) {
                return fn(b) - fn(a);
            };
            //AllNodes.sort(sortExpression);
            rootNodes.sort(sortExpression).forEach(function (x) {
                sortChildren(x);

            });
            if (hasRender) {
                //container.empty();
                this.render();
            }
            return this;
        }
        this.createNode = function (id, name, icon, level, parentNode, bindingData,isCategory) {
            var node = new treeNode(
                id,
                name,
                icon,
                level,
                parentNode,
                bindingData,
                isCategory
                );
            return node;
        }
        function createTreeNode(ori, level) {

            var node = new treeNode(
                selectors.idSelector(ori),
                selectors.nameSelector(ori),
                selectors.iconSelector(ori),
                level,//selectors.levelSelector(ori),
                null,
                ori,
                selectors.isCategorySelector(ori)
                );
            return node;
        }
        var rootNodes = [];
        for (var i = 0; i < list.length; i++) {
            var node = createTreeNode(list[i], 0);
            AllNodes.push(node);
        }



        for (var i = 0; i < AllNodes.length; i++) {
            var node = AllNodes[i];
            node.parentNode = AllNodes.find(function (element, index, all) {
                if (!node.bindingData || !element.bindingData) return false;
                return selectors.idSelector(element.bindingData) === selectors.parentNodeSelector(node.bindingData);
            });

            node.addChildren(AllNodes.filter(function (element, index, all) {
                if (!node.bindingData || !element.bindingData) return false;
                return selectors.idSelector(node.bindingData) === selectors.parentNodeSelector(element.bindingData);
            }));

            if (!node.parentNode)
                rootNodes.push(node);
        }

        this.render = function () {
            
            var $dl = $('<dl>');
            for (var i = 0; i < rootNodes.length; i++) {
                $dl.append(rootNodes[i].render());
            }
            
            container.empty().append($dl).addClass(constant.css.rootClass);
            hasRender = true;
            return ths;
        }

        this.withFieldName = function (name) {
            variable.fieldName = name;
            return ths;
        }
        this.showCheckbox = function () {
            variable.showCheckbox = true;
            if (!hasRender) return ths;
            AllNodes.forEach(function (x) {
                x.showCheckbox();
            })
            return ths;
        }
        this.hideCheckbox = function () {
            variable.showCheckbox = false;
            if (!hasRender) return ths;
            AllNodes.forEach(function (x) {
                x.hideCheckbox();
            })
            return ths;
        }
        this.expandAll = function () {
            variable.isExpand = true;
            if (!hasRender) return ths;
            AllNodes.forEach(function (x) {
                x.expand();
            })
            return ths;
        }
        this.closeAll = function () {
            variable.isExpand = false;
            if (!hasRender) return ths;
            AllNodes.forEach(function (x) {
                x.close();
            })
            return ths;
        }
        this.getNode = function (predicate) {

            return AllNodes.find(predicate);
        }
        this.getNodeAt = function (index) {
            return AllNodes[index];
        }
        this.getNodes = function (predicate) {
            return AllNodes.filter(predicate);
        }
        this.createNodeByOriData = function (oriData, level) {
            return createTreeNode(oriData, level);
        }



        this.expandByLevel = function (level) {
            AllNodes.filter(function (x) { return x.level <= level && x.children().length > 0; }).forEach(function (x) { x.expand() });
            return ths;
        }
        this.closeByLevel = function (level) {
            AllNodes.filter(function (x) { return x.level === level && x.children().length > 0; }).forEach(function (x) { x.close() });
            return ths;
        }
        this.toggleByLevel = function (level) {
            AllNodes.filter(function (x) { return x.level === level && x.children().length > 0; }).forEach(function (x) { x.toggle() });
            return ths;
        }
        this.nodeClick = function (callback) {
            callbacks.nodeClick = callback;
            if (hasRender && callback && typeof callback === 'function') {
                AllNodes.forEach(function (x) {
                    x.bindEvent('click', callback);
                });
            }
            return ths;
        }
        this.nodeMouseLeave = function (callback) {
            callbacks.nodeMouseLeave = callback;
            if (hasRender && callback && typeof callback === 'function') {
                AllNodes.forEach(function (x) {
                    x.bindEvent('mouseleave', callback);
                });
            }
            return ths;
        }
        this.nodeMouseEnter = function (callback) {
            callbacks.nodeMouseEnter = callback;
            if (hasRender && callback && typeof callback === 'function') {
                AllNodes.forEach(function (x) {
                    x.bindEvent('mouseenter', callback);
                });
            }
            return ths;
        }
        this.nodeCheckChange = function (callback) {
            callbacks.nodeCheckChange = callback;
            if (hasRender && callback && typeof callback === 'function') {
                AllNodes.forEach(function (x) {
                    x.bindEvent('change', callback, true);
                });
            }
            return ths;
        }
        this.removeNode = function (node) {

            var index = AllNodes.indexOf(node);
            if (index > -1) {

                AllNodes.splice(index, 1);

            }

            node.destroy();
            return ths;
        }
        this.removeNodeWhere = function (predicate) {
            var nodes = AllNodes.filter(predicate);
            for (var i = 0; i < nodes.length; i++) {
                ths.removeNode(node);
            }
            return ths;
        }
        this.multiSelect = function (bool) {
            variable.multiSelect = bool;
            if (hasRender) {
                AllNodes.forEach(function (node) { node.refresh(); });
            }
            return ths;
        }
        this.getSelectedNode = function () {
            return selectedNode;
        }
        this.getSelectedNodes = function () {
            return AllNodes.filter(function (x) {
                return x.isCheck();
            });
        }
    }


    $.fn.extend({
        treeView: function (listData, fieldSelectors, refresh) {
            var $this = this;
            if (refresh) {
                $this.empty();
            }
            return new treeView($this, listData, fieldSelectors);
        }
    })
})(jQuery);